import javax.swing.Timer;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.Graphics2D;
import java.util.*;
/**
 * Clase del mundo de la simulación, ontrola los elementos que interactúan.
 * Implementa un ActionListener para los eventos del panel.
 * @version 2
 */
public class MyWorld implements ActionListener {
   public static int WIDTH = 600;  // in pixels
   public static int HEIGHT = 500; // in pixels
   private double t;              // Simulation time
   private Timer pase;
   private double delta_t;        // in seconds
   private double refreshPeriod;  // in seconds
   
   private Vector2D g;
   private float viscosity;
   private PhysicsLabPanel labPanel;
   private Vector<PhysicsElement> elements;
   private boolean borrar;

   /**
    * Constructor del mundo de la simulación.
    */
   public MyWorld(){
      t = 0;
      refreshPeriod = 0.06;      // 80 [ms]
      delta_t = 0.0001;          // 0.1 [ms]
      pase = new Timer((int)(refreshPeriod*1000), this);
      g = new Vector2D(0, 0);
      viscosity = 0.0f;
      elements = new Vector<PhysicsElement>();
      labPanel = null;
   }
   /**
    * Configura un panel para la simulación.
    * @param p panel
    */
   public void setPanel (PhysicsLabPanel p) {
      labPanel = p;
   }
   /**
    * Configura el delta de tiempo de la simulación.
    * @param delta 
    */
   public void setDelta_t(double delta) {
      delta_t = delta;
   }
   /**
    * Configura el tiempo de refresco de la simulación.
    * @param rp tiempo en milisegundos
    */
   public void setRefreshPeriod (double rp) {
      refreshPeriod = rp;
      pase.setDelay((int)(refreshPeriod*1000));
   }
   /**
    * Configura la gravedad que afecta a la simulación.
    * @param gravity vector de gravedad.
    */
   public void setGravity (Vector2D gravity) {
      g = gravity;
   }
   /**
    * Configura la viscosidad del medio.
    * @param v viscosidad.
    */
   public void setViscosity(float v){
      viscosity = v;
   }
   /**
    * Agrega un elemento al vector de elementos en la simulación.
    * @param e elemento.
    */
   public void addElement(PhysicsElement e) {
      elements.add(e);
      labPanel.repaint();
   }
   /**
    * Función que inicia la simulación con velocidad cero de sus elementos.
    */
   public void start() {
      if(pase.isRunning()) return;
      for (PhysicsElement e: elements) {
         e.setInitialState(g, viscosity);
         e.reset(); // agregado
      }
      labPanel.desableMouseListener();
      pase.restart();
   }
   /**
    * Función que detiene la simulación.
    */
   public void stop(){
      pase.stop();
      labPanel.enableMouseListener();
   }
   /**
    * Función que resume la simulación.
    */
   public void resume() {
      if(pase.isRunning()) return;
      for (PhysicsElement e: elements)
         e.setInitialState(g, viscosity);
      labPanel.desableMouseListener();
      pase.start();
   }
   /**
    * Función que simula y configura los elementos a medida que avanze el tiempo.
    * Esta función es llamada por el evento del timer.
    * @param event evento generado.
    */
   public void actionPerformed (ActionEvent event) {  // simulate passing time
      double nextStop=t+refreshPeriod;
      for (; t<nextStop; t+=delta_t){
      for (PhysicsElement e: elements)
         e.computeNextState(delta_t, g, viscosity);
      for (PhysicsElement e: elements)
         e.updateState();
      }
      labPanel.repaint();
   }
   /**
    * Función que pinta los elementos de la simulación.
    * @param g2d lienzo donde se pintarán los elementos.
    */
   public void paintComponent(Graphics2D g2d){
      for (PhysicsElement e: elements)
         if (e instanceof Drawable)
            ((Drawable)e).draw(g2d);
   }
   /**
    * Función para encontrar el elemento que contiene las coordenadas.
    * @param x coordenada X.
    * @param y coordenada Y.
    * @return elemento dibujable.
    */
   public Drawable find(int x, int y) {
      for (PhysicsElement e: elements)
         if (e instanceof Drawable){
            Drawable de = (Drawable) e;
            if (de.contains(x,y)) return de;
         }
      return null;
   }  
   /**
    * Función que busca un elemento al cual se le pueden acoplar elementos.
    * @param x coordenada X.
    * @param y coordenada Y.
    * @return elemento encontrado.
    */
   public AttachableElement findAttachableElement(int x, int y) {
      for (PhysicsElement e :elements)
         if (e instanceof AttachableElement) {
            AttachableElement ae =(AttachableElement) e;
            if (ae instanceof Drawable)
            if (((Drawable)ae).contains(x,y)) return ae;
         }
      return null;
   }
   /**
    * Función de consulta si está o no corriendo la simulación.
    * @return estado de la simulación.
    */
   public boolean isRunning(){
       return (pase.isRunning());
   }
   /**
    * función que llama a la función del panel para cambiar el cursor a cruz e
    * indicar si se va a borrar el elemento a seleccionar.
    * @param a 
    */
   public void changeCursor(boolean a) {
       labPanel.changeCursor(a);
       this.borrar = a;
   }
   /**
    * Función que devuelve el estado de si se borra o no el elemento a selecionar.
    * @return estado.
    */
   public boolean isBorrar(){
       return borrar;
   }
   /**
    * Función que borra el elemento que contiene las coordenadas.
    * @param x coordenada X.
    * @param y coordenada Y.
    */
   public void delElement(int x, int y) {
      for (PhysicsElement e: elements)
         if (e instanceof Drawable){
            Drawable de = (Drawable) e;
            if (de.contains(x,y)) {
                elements.remove(e);
                break;
            }
         }
      borrar = false;
      labPanel.changeCursor(borrar);
      labPanel.repaint();
   }
}
